/* eslint-disable no-bitwise */
/**
 * 默认报告样式
 *
 * Created by snowingsea on 2021/01/19.
 */
const fs = require('fs-extra');
const path = require('path');
const compressing = require('compressing');
// eslint-disable-next-line import/no-extraneous-dependencies
const canvas = require('canvas');
const randomString = require('randomstring');
const eCharts = require('echarts');
const ImageModule = require('docxtemplater-image-module-free');
const moment = require('moment');

const files = require('./../../../files');
const functions = require('./../../../functions');
const tests = require('./../../../tests');
const utility = require('./../../../utility');
const BaseReporter = require('./../base-report');
const classCreator = require('./../base-report-test-case-transformer');
const Docx = require('./../adapter/docx');
const { getCountableKinds } = require('../../../file-elements');
const DefaultDataReport = require('./default-data-adapter');
const TargetMachineDataReport = require('./target-machine-data-adapter');
const Excel = require('./excel');

class DefaultReporter extends BaseReporter {
  constructor(versionId, locale, version = null, project = null, creator = null, outputDir = null) {
    super(versionId, locale, version, project, creator, outputDir);
    this.setDataAdapter(new DefaultDataReport());
    this.fileExcelReport = null;
  }

  /**
   * 报告生成
   */
  generate() {
    const result = {
      cachePath: this.outputDir,
    };
    return new Promise((resolve, reject) => {
      this.spliteByFile()
        .then(() => this.generateSummary())
        .then(() => {
          const fileName = `${this.project.projectName}-${this.version.versionName}.${this.getReportName()}${this.getDataAdapter() instanceof TargetMachineDataReport ? `.${this.i18n.__('tests.targetMachine')}` : ''}.zip`;
          result.reportPath = path.join(this.outputDir, fileName);
          const reportsDir = path.join(this.outputDir, this.project.projectName);
          return compressing.zip.compressDir(reportsDir, result.reportPath);
        })
        .then(() => {
          resolve(result);
        })
        .catch(reject);
    });
  }

  static handleOverview(overviews, enabledCoverageKind) {
    overviews.forEach((overview) => {
      overview.coverageInfo = [];
      enabledCoverageKind.forEach((coverageKind) => {
        let coverage = '-';
        if (overview[coverageKind.totalKey]) {
          coverage = overview[coverageKind.coveredKey]
            / overview[coverageKind.totalKey];
          coverage = `${Math.round(coverage * 10000) / 100}%`;
        }
        overview.coverageInfo.push(
          {
            coverage,
            total: overview[coverageKind.totalKey],
            covered: overview[coverageKind.coveredKey],
          }
        );
      });
    });
    return overviews;
  }

  /**
   * 生成测试报告总览
   * @returns {Promise}
   */
  generateSummary() {
    const summary = {
      reportTime: moment().format('YYYY/MM/DD HH:mm:ss'),
      reportFooterText: this.reportFooterText,
      isIntegrationTest: this.isIntegrationTest(),
      reportName: this.getReportName(),
    };
    const coverageDistributionImageSize = { height: 160, width: 540 };
    const testCaseResultsImageSize = { height: 140, width: 240 };
    const extensions = Object.values(this.version.languages)
      .reduce((arr, item) => arr.concat(item.suffixes), []);
    return this.getDataAdapter().getVersionOverview(this.versionId)
      .then((overview) => {
        const enabledCoverageKind = [];
        ['statement', 'branch', 'mcdc'].forEach((key) => {
          if (this.version.coverages & tests.coverageKind[key]) {
            enabledCoverageKind.push(
              {
                coverageKey: `${key}Coverage`,
                totalKey: `${key}Total`,
                coveredKey: `${key}Covered`,
                name: this.i18n.__(`tests.${key}Coverage`),
              }
            );
          }
          overview[`${key}Coverage`] = '-';
          if (overview[`${key}Total`]) {
            overview[`${key}Coverage`] = overview[`${key}Covered`] / overview[`${key}Total`];
            overview[`${key}Coverage`] = `${Math.round(overview[`${key}Coverage`] * 10000) / 100}%`;
          }
        });
        summary.overview = overview;
        summary.enabledCoverageKind = enabledCoverageKind;
        return this.getDataAdapter().getTestResultOverview(this.versionId);
      })
      .then((testResultOverview) => {
        const testsOverview = {
          succeed: 0,
          nonStrictSucceed: 0,
          failed: 0,
          noResult: 0,
          exception: 0,
          codeDefect: 0,
        };
        Object.keys(testResultOverview).forEach((key) => {
          Object.keys(testResultOverview[key]).forEach((testResultKind) => {
            testResultKind = Number(testResultKind);
            if (!testResultKind) {
              return; // 过滤掉未执行(unknown)
            }
            const testResultKey = BaseReporter.getTestResultKindKey(testResultKind);
            testsOverview[testResultKey] += testResultOverview[key][testResultKind];
          });
        });
        testsOverview.total = Object.keys(testsOverview)
          .reduce((total, key) => total + testsOverview[key], 0);
        summary.testsOverview = testsOverview;
        return this.getDataAdapter().getFunctionCoverageDistribution(this.versionId);
      })
      .then((functionsCoverage) => {
        summary.functionsCoverage = functionsCoverage;
        summary.functionsCoverage.interval = 25;
        return Promise.resolve();
      })
      .then(() => {
        if (!this.isIntegrationTest()) {
          return Promise.resolve();
        }
        summary.integrationResult = [];
        const filesMap = new Map();
        return this.getDataAdapter().getFiles(this.versionId)
          .then((versionFiles) => {
            versionFiles.forEach((file) => {
              filesMap.set(file._id.toString(), file.fileName);
            });
            return this.getDataAdapter().getFunctions(this.versionId);
          })
          .then((funcs) => {
            let queue = Promise.resolve();
            utility.arrayChunk(funcs, 20).forEach((funcsGroup) => {
              queue = queue.then(
                () => Promise.all(
                  funcsGroup.map((func) => this.getDataAdapter().getIntegratedFuncs(func._id, true)
                    .then((integratedFuncs) => {
                      summary.integrationResult.push(
                        {
                          fileName: filesMap.has(func.fileId.toString()) ? filesMap.get(func.fileId.toString()) : '',
                          functionName: func.functionName,
                          integrationFunctions: integratedFuncs
                            .map((relation, index) => ({ name: `${relation.toFuncName}${index === integratedFuncs.length - 1 ? '' : ','}` })),
                          invokedFuncCoverage: func.invokedFuncTotal ? `${Math.round((func.invokedFuncCovered / func.invokedFuncTotal) * 10000) / 100}%` : '-',
                          invokeCoverage: func.invokeTotal ? `${Math.round((func.invokeCovered / func.invokeTotal) * 10000) / 100}%` : '-',
                        }
                      );
                    }))
                )
              );
            });
            return queue
              .then(() => {
                summary.integrationResult = summary.integrationResult.map((result, index) => {
                  result.index = index + 1;
                  return result;
                });
              });
          });
      })
      .then(() => this.getDataAdapter().getFilesOverview(this.versionId, extensions))
      .then(({ filesOverview, fileStatusCount }) => {
        summary.sourceFilesOverview = DefaultReporter.handleOverview(
          filesOverview,
          summary.enabledCoverageKind
        );
        summary.sourceFileStatusCount = fileStatusCount.reduce((pre, status) => {
          pre[status.status] = status.count;
          return pre;
        }, {});
        return this.getDataAdapter().getFunctionsOverview(this.versionId, { pageSize: 999999 });
      })
      .then((functionsOverview) => {
        summary.functionsOverview = DefaultReporter.handleOverview(
          functionsOverview,
          summary.enabledCoverageKind
        );
      })
      .then(() => Promise.all([
        this.generateCoverageDistributionImage(
          summary.functionsCoverage,
          coverageDistributionImageSize
        ),
        this.generateTestCaseResultsImage(
          summary.testsOverview,
          testCaseResultsImageSize
        ),
      ]))
      .then(([coverageDistributionImage, testCaseResultsImage]) => {
        summary.coverageDistributionImage = coverageDistributionImage.output;
        summary.testCaseResultsImage = testCaseResultsImage.output;
        const docx = new Docx(path.join(__dirname, 'template', 'docx', 'summary-template.docx'));
        docx.getAdapterInstance().attachModule(new ImageModule({
          centered: false,
          getImage: (output) => fs.readFileSync(output),
          getSize: (img, tagValue) => {
            switch (tagValue) {
              case summary.coverageDistributionImage:
                return [coverageDistributionImageSize.width, coverageDistributionImageSize.height];
              case summary.testCaseResultsImage:
                return [testCaseResultsImageSize.width, testCaseResultsImageSize.height];
              default:
                return [0, 0];
            }
          },
        }));

        return docx.generate(
          summary,
          path.join(
            this.outputDir,
            this.project.projectName,
            this.version.versionName,
            'summary.docx'
          )
        )
          .then(() => {
            fs.remove(coverageDistributionImage.output);
            fs.remove(testCaseResultsImage.output);
          });
      });
  }

  /**
   * 处理单个文件，生成测试用例报告
   * @param {Object}} file 单个文件信息
   * @param {String} versionDir 版本目录
   * @returns {Promise}
   */
  handleFile(file, versionDir) {
    return new Promise((resolve, reject) => {
      const { fileName } = file;
      const reportDocxPath = `${path.join(versionDir, path.dirname(file.path), fileName)}.docx`;
      const reportExcelPath = `${path.join(versionDir, path.dirname(file.path), fileName)}.xlsx`;
      super.handleFile(file, versionDir)
        .then((res) => {
          const { typeSystem, functions, resultCount } = res;
          if (!resultCount) {
            return Promise.resolve();
          }
          const fileSummary = DefaultReporter.getSummaryOfFile(functions);
          const functionsData = this.getFunctionDetailsOfFile(typeSystem, functions);
          return Promise.all([
            this.generateFileDocxReport(reportDocxPath, fileName, fileSummary, functionsData),
            this.generateFileExcelReport(reportExcelPath, functionsData),
          ]);
        })
        .then(() => {
          resolve({ reportPath: reportDocxPath });
        })
        .catch(reject);
    });
  }

  /**
   * 生成单个文件的docx报告
   * @param {String} reportPath 报告路径
   * @param {Object} file 文件信息
   * @param {Object} typeSystem 类型系统
   * @param {Array} functionList 函数列表
   * @returns {Promise}
   */
  generateFileDocxReport(reportPath, fileName, fileSummary, functionsData) {
    const docxTemplate = new Docx(path.join(__dirname, 'template', 'docx', 'file-template.docx'));
    return fs.ensureDir(path.dirname(reportPath))
      .then(() => docxTemplate.generate(
        {
          fileName,
          summary: fileSummary,
          functions: functionsData,
          isIntegrationTest: this.isIntegrationTest(),
          reportName: this.getReportName(),
          reportFooterText: this.reportFooterText,
        },
        reportPath
      ));
  }

  /**
   * 生成单个文件的Excel报告
   * @param {String} reportPath 报告路径
   * @param {Object} file 文件信息
   * @param {Object} typeSystem 类型系统
   * @param {Array} functionList 函数列表
   * @returns {Promise}
   */
  async generateFileExcelReport(reportPath, functionsData) {
    await fs.ensureDir(path.dirname(reportPath));
    this.fileExcelReport = new Excel();
    return this.fileExcelReport.generateFileReport(reportPath, functionsData);
  }

  /**
   * 获取单个文件概要统计
   * @param {Array} functions 函数列表
   * @returns {Object}
   */
  static getSummaryOfFile(functions) {
    const summary = {
      functionCount: functions.filter(({ kind }) => getCountableKinds().includes(kind)).length,
      tests: {
        total: 0,
        succeed: 0,
        nonStrictSucceed: 0,
        failed: 0,
        noResult: 0,
        exception: 0,
        codeDefect: 0,
      },
    };
    functions.forEach((func) => {
      summary.tests.total += func.tests.length;
      func.tests.forEach((test) => {
        const key = BaseReporter.getTestResultKindKey(test.resultKind);
        test.resultKindName = key;
        if (summary.tests[key] === undefined) {
          return;
        }
        summary.tests[key] += 1;
      });
    });
    return summary;
  }

  /**
   * 获取函数列表详细信息
   * @param {Object} typeSystem 类型系统
   * @param {Array} functions 函数列表
   * @returns {Array}
   */
  getFunctionDetailsOfFile(typeSystem, functions) {
    const resFunctions = [];
    functions.sort((a, b) => {
      if (a.index < b.index) return -1;
      if (a.index > b.index) return 1;
      return 0;
    });
    functions.forEach((func) => {
      if (!func.tests.length) {
        return;
      }
      resFunctions.push({
        functionName: func.functionName,
        description: func.description,
        udfs: func.udfs,
        summary: this.getSummaryOfFunction(func),
        tests: this.getTestDetailsOfFunction(typeSystem, func),
        integrationFunctions: func.integrationFunctions,
        integrationFunctionsCount: func.integrationFunctions.length,
      });
    });
    return resFunctions;
  }

  /**
   * 获取某个函数的概要统计
   * @param {Object}} func 函数信息
   * @returns {Object}
   */
  getSummaryOfFunction(func) {
    let returnType = 'void';
    if (func.variables.output && func.variables.output['%']) {
      returnType = func.variables.output['%']['@type'];
    }
    let { functionNameArgs } = func;
    if (!functionNameArgs) {
      const params = [];
      if (func.variables.variables && func.variables.variables.params) {
        Object.keys(func.variables.variables.params).forEach((paramName) => {
          params.push(`${func.variables.variables.params[paramName]['@type']} ${paramName}`);
        });
      }
      functionNameArgs = `${func.functionName}(${params.join(', ')})`;
    }
    const summary = {
      declaration: `${returnType} ${functionNameArgs};`,
      testCount: func.tests.length,
    };
    const { coverages } = this.version;
    // eslint-disable-next-line no-bitwise
    if (func.statementTotal && (coverages & tests.coverageKind.statement) > 0) {
      summary.statementCoverage = func.statementCovered / func.statementTotal;
      summary.statementCoverage = `${Math.round(summary.statementCoverage * 10000) / 100}%`;
    } else if ((coverages & tests.coverageKind.statement) > 0) {
      summary.statementCoverage = '-';
    }
    // eslint-disable-next-line no-bitwise
    if (func.branchTotal && (coverages & tests.coverageKind.branch) > 0) {
      summary.branchCoverage = func.branchCovered / func.branchTotal;
      summary.branchCoverage = `${Math.round(summary.branchCoverage * 10000) / 100}%`;
    } else if ((coverages & tests.coverageKind.branch) > 0) {
      summary.branchCoverage = '-';
    }
    // eslint-disable-next-line no-bitwise
    if (func.mcdcTotal && (coverages & tests.coverageKind.mcdc) > 0) {
      summary.mcdcCoverage = func.mcdcCovered / func.mcdcTotal;
      summary.mcdcCoverage = `${Math.round(summary.mcdcCoverage * 10000) / 100}%`;
    } else if ((coverages & tests.coverageKind.mcdc) > 0) {
      summary.mcdcCoverage = '-';
    }
    // eslint-disable-next-line no-bitwise
    if (func.invokeTotal && (coverages & tests.coverageKind.invoke) > 0) {
      summary.invokeCoverage = func.invokeCovered / func.invokeTotal;
      summary.invokeCoverage = `${Math.round(summary.invokeCoverage * 10000) / 100}%`;
    } else if ((coverages & tests.coverageKind.invoke) > 0) {
      summary.invokeCoverage = '-';
    }
    // eslint-disable-next-line no-bitwise
    if (func.invokedFuncTotal && (coverages & tests.coverageKind.invokedFunc) > 0) {
      summary.invokedFuncCoverage = func.invokedFuncCovered / func.invokedFuncTotal;
      summary.invokedFuncCoverage = `${Math.round(summary.invokedFuncCoverage * 10000) / 100}%`;
    } else if ((coverages & tests.coverageKind.invokedFunc) > 0) {
      summary.invokedFuncCoverage = '-';
    }
    const coverageArr = [];
    if (summary.invokedFuncCoverage) {
      coverageArr.push({
        name: '函数覆盖率',
        value: summary.invokedFuncCoverage,
      });
    }
    if (summary.invokeCoverage) {
      coverageArr.push({
        name: '调用覆盖率',
        value: summary.invokeCoverage,
      });
    }
    if (summary.statementCoverage) {
      coverageArr.push({
        name: '语句覆盖率',
        value: summary.statementCoverage,
      });
    }
    if (summary.branchCoverage) {
      coverageArr.push({
        name: '分支覆盖率',
        value: summary.branchCoverage,
      });
    }
    if (summary.mcdcCoverage) {
      coverageArr.push({
        name: 'MC/DC覆盖率',
        value: summary.mcdcCoverage,
      });
    }
    summary.coverages = coverageArr;
    // summary.codeCoverageDetails = this.sampleReportDetail();
    summary.codeCoverageDetails = func.codeCoverageDetails;
    return summary;
  }

  /**
   * 获取用例列表详细信息
   * @param {Object} typeSystem 类型系统
   * @param {Object} func 函数信息
   */
  getTestDetailsOfFunction(typeSystem, func) {
    const resTests = [];
    func.tests.sort((a, b) => {
      if (a.number < b.number) return -1; // eslint-disable-line no-underscore-dangle
      if (a.number > b.number) return 1; // eslint-disable-line no-underscore-dangle
      return 0;
    });
    const funcVariables = functions.getFunctionVariableJson(func.variables);
    if (!this.isIntegrationTest()) {
      typeSystem = files.getTypesJson(func.fileId, typeSystem);
    }
    func.tests.forEach((test) => {
      const testCase = tests.getJson(test, this.i18n);
      const ReportTestCaseTransformer = classCreator(this.isIntegrationTest() && 'integrationTest');
      const transformer = new ReportTestCaseTransformer(
        testCase,
        funcVariables,
        typeSystem,
        this.i18n
      );
      transformer.transform();

      const { coverages } = this.version;
      let statementCoverage; let branchCoverage; let mcdcCoverage;
      let invokeCoverage; let invokedFuncCoverage;
      if (func.statementTotal && (coverages & tests.coverageKind.statement) > 0) {
        statementCoverage = test.statementCovered / func.statementTotal;
        statementCoverage = `${Math.round(statementCoverage * 10000) / 100}%`;
      } else if ((coverages & tests.coverageKind.statement) > 0) {
        statementCoverage = '-';
      }
      if (func.branchTotal && (coverages & tests.coverageKind.branch) > 0) {
        branchCoverage = test.branchCovered / func.branchTotal;
        branchCoverage = `${Math.round(branchCoverage * 10000) / 100}%`;
      } else if ((coverages & tests.coverageKind.branch) > 0) {
        branchCoverage = '-';
      }
      if (func.mcdcTotal && (coverages & tests.coverageKind.mcdc) > 0) {
        mcdcCoverage = test.mcdcCovered / func.mcdcTotal;
        mcdcCoverage = `${Math.round(mcdcCoverage * 10000) / 100}%`;
      } else if ((coverages & tests.coverageKind.mcdc) > 0) {
        mcdcCoverage = '-';
      }
      if (func.invokeTotal && (coverages & tests.coverageKind.invoke) > 0) {
        invokeCoverage = test.invokeCovered / func.invokeTotal;
        invokeCoverage = `${Math.round(invokeCoverage * 10000) / 100}%`;
      } else if ((coverages & tests.coverageKind.invoke) > 0) {
        invokeCoverage = '-';
      }
      if (func.invokedFuncTotal && (coverages & tests.coverageKind.invokedFunc) > 0) {
        invokedFuncCoverage = test.invokedFuncCovered / func.invokedFuncTotal;
        invokedFuncCoverage = `${Math.round(invokedFuncCoverage * 10000) / 100}%`;
      } else if ((coverages & tests.coverageKind.invokedFunc) > 0) {
        invokedFuncCoverage = '-';
      }
      resTests.push({
        index: test.number,
        description: testCase.description,
        identifier: testCase.identifier,
        requirementInfo: testCase.requirementInfo,
        resultKind: test.resultKindName,
        udfs: test.udfs,
        detail: {
          prelude: transformer.prelude,
          input: transformer.input,
          output: transformer.output,
          result: transformer.result,
        },
        isIntegrationTest: this.isIntegrationTest(),
        group: test.group ? test.group.map((num) => `${this.i18n.__('tests.group')}${num} / `).join('') : '',
        statementCoverage,
        branchCoverage,
        mcdcCoverage,
        invokeCoverage,
        invokedFuncCoverage,
      });
    });
    return resTests;
  }

  /**
   * 获取图片路径
   * @return {string}
   */
  getImagePath() {
    return path.join(this.outputDir, `${randomString.generate(16)}.png`);
  }

  /**
   * 获取EChart图表数据
   * @param {Object} options 配置信息
   * @param {Object} canvasSize 画布大小
   * @return {Buffer}
   */
  static getEChartsImage(options, canvasSize) {
    const ratio = 4;
    canvas.registerFont(path
      .join(__dirname, '..', 'fonts', 'SourceHanSansCN-Bold.otf'), { family: 'Arial' });
    const imageCanvas = canvas.createCanvas(ratio * canvasSize.width, ratio * canvasSize.height);
    eCharts.setCanvasCreator(() => imageCanvas);
    const chart = eCharts.init(imageCanvas);
    chart.setOption(options);
    // return imageCanvas.toBuffer();
    const buf = imageCanvas.toBuffer();
    chart.dispose();
    return buf;
  }

  /**
   * 获取所有函数覆盖率统计直方图
   * @param {Object} resultData 函数覆盖率统计
   * @param {Object} size 图片大小
   * @returns {Promise}
   */
  generateCoverageDistributionImage(resultData, size) {
    return new Promise((resolve, reject) => {
      const styles = {
        statement: {
          name: this.i18n.__('tests.statementCoverage'),
          color: '#56abfa',
        },
        branch: {
          name: this.i18n.__('tests.branchCoverage'),
          color: '#54b184',
        },
        mcdc: {
          name: this.i18n.__('tests.mcdcCoverage'),
          color: '#7c88f7',
        },
      };
      const source = [];
      const series = [];
      // 类目
      const category = ['distribution'];
      Object.keys(resultData).forEach((coverageName) => {
        if (styles[coverageName]) {
          category.push(styles[coverageName].name);
          series.push({ type: 'bar', color: styles[coverageName].color });
        }
      });
      source.push(category);
      // 数据
      for (let i = 0; i < Math.floor(100 / resultData.interval) + 1; i += 1) {
        // 区间
        const leftNumber = i * resultData.interval;
        const rightNumber = (i + 1) * resultData.interval;
        const item = [`[${leftNumber}, ${Math.min(rightNumber, 100)}`];
        if (rightNumber > 100) {
          item[0] += ']';
        } else {
          item[0] += ')';
        }
        // 柱状数据
        Object.keys(resultData).forEach((coverageName) => {
          if (styles[coverageName]) {
            item.push(resultData[coverageName].distribution[i]);
          }
        });
        source.push(item);
      }
      const options = {
        grid: {
          top: '10%',
          bottom: '10%',
          left: '10%',
          right: '10%',
        },
        legend: {
          height: '10%',
          textStyle: {
            fontSize: 30,
          },
        },
        tooltip: {},
        dataset: { source },
        xAxis: {
          type: 'category',
          axisLabel: {
            fontSize: 30,
          },
        },
        yAxis: {
          axisLabel: {
            fontSize: 30,
          },
        },
        series,
      };
      const outputPath = this.getImagePath();
      utility.writeFile(DefaultReporter.getEChartsImage(options, size), outputPath)
        .then(() => {
          resolve({ output: outputPath });
        })
        .catch(reject);
    });
  }

  /**
   * 获取测试用例结果图
   * @param {Object} resultData 测试结果数据
   * @param {Object} size 图片大小
   * @returns {Promise}
   */
  generateTestCaseResultsImage(resultData, size) {
    const totalTestsCnt = Object.keys(resultData)
      .reduce((total, key) => total + resultData[key], 0);
    return new Promise((resolve, reject) => {
      const styles = {
        succeed: {
          name: '通过',
          color: '#94cd8a',
        },
        nonStrictSucceed: {
          name: '通过*',
          color: '#D1DA11',
        },
        failed: {
          name: '未通过',
          color: '#fe807f',
        },
        noResult: {
          name: '无结果',
          color: '#ddd',
        },
        codeDefect: {
          name: '运行时错误',
          color: '#ff9000',
        },
        exception: {
          name: '执行异常',
          color: '#fecd67',
        },
        default: {
          name: '',
          color: '#d1d1d1',
        },
      };
      const serie = {
        name: 'results',
        type: 'pie',
        data: [],
        top: '10%',
        avoidLabelOverlap: true,
        radius: '70%',
        startAngle: 180,
        minShowLabelAngle: 2e-8,
        label: {
          normal: {
            textStyle: {
              fontSize: 30,
            },
          },
        },
        labelLine: {
          normal: {
            length: 20,
            length2: 40,
          },
        },
        itemStyle: {
          normal: {
            color: (params) => styles[params.data.kindName].color,
          },
        },
      };
      resultData = totalTestsCnt ? resultData : { default: 0 };
      Object.keys(resultData).forEach((kindName) => {
        if (!styles[kindName]) {
          return;
        }
        serie.data.push({
          value: resultData[kindName],
          name: styles[kindName].name,
          kindName,
        });
      });
      const options = {
        title: {
          text: '用例执行结果统计',
          x: 'center',
          top: 0,
          textStyle: {
            fontweight: 'bold',
            fontSize: 30,
          },
        },
        legend: {
          show: false,
          orient: 'vertical',
          x: 'left',
        },
        series: [serie],
      };
      const outputPath = this.getImagePath();
      utility.writeFile(DefaultReporter.getEChartsImage(options, size), outputPath)
        .then(() => {
          resolve({ output: outputPath });
        })
        .catch(reject);
    });
  }
}

/**
 * Export model definition object.
 */
module.exports = DefaultReporter;
